import java.util.ArrayList;

/**
 * Created by IntelliJ IDEA.
 * User: Rajeev
 * Date: Dec 27, 2009
 * Time: 2:55:07 PM
 * To change this template use File | Settings | File Templates.
 */
public class RecursiveDescent {
    dfa d;
    String[] tokens;
    int index = 0;
    boolean error = false;

    public RecursiveDescent(dfa dfa) {
        this.d = dfa;
        ArrayList<String> list = dfa.getTokens();
        tokens = new String[list.size()];

        for (int i = 0; i < tokens.length; i++) {
            tokens[i] = list.get(i);
        }
    }

    public Node test() throws SyntaxException {
        Node s = schema();
        System.out.println(s.toString());
        return s;
    }

    // used to ignore initial xmlns elements....they don't belong to dsd spec.
    private void eat_xmlns() throws SyntaxException {
        String current = d.lookAhead();
        while ("xmlns".equalsIgnoreCase(d.lookAhead())) {
            d.nextToken();
            current = d.nextToken();
            if (":".equals(current)) {
                current = d.nextToken(); // local part.
                d.nextToken(); // =
            }
            d.nextToken(); // ".....uri.........."
            current = d.lookAhead();
        }
        d.resetLookAhead();
    }

    /*
 SCHEMA	 ::=  	<dsd  ( root="PENAME" )? >
( RULE  |  DEFINITION  |  SCHEMA )*
</dsd>
    */
    private Node schema() throws SyntaxException {
        Node n = new Node();
        n.type = "dsd";
        n.meta = "SCHEMA";

        if (equalsSequence("<", "dsd")) {
            // pename
            if (!equalsLookAhead(">")) {
                // TODO - attributes
                eat_xmlns(); // remove xmlns.


            } else {     // no pename
                // nothing to do
            }

            if (equalsSequence(">")) {
                /// content
                String first = d.lookAhead();
                String second = d.lookAhead();
                while (first.equals("<") && !second.equals("/")) {
                    Node child = null;
                    if (equalsOR(second, "declare", "require", "if", "unique", "pointer")) {
                        child = rule();
                    } else if (equalsOR(second, "contenttype", "stringtype", "boolexp")) {
                        child = definition();
                    } else if (equalsOR(second, "rule")) {
                        // todo - requires left factoring
                        String third = d.lookAhead();
                        if (equalsOR(third, "ref")) {
                            child = rule();
                        } else if (equalsOR(third, "id")) {
                            child = definition();
                        } else {
                            child = new Node();
                            child.type = "ERROR";
                        }

                    } else if (equalsOR(second, "dsd")) {
                        child = schema();

                    }
                    n.addChild(child);

                    first = d.lookAhead();
                    second = d.lookAhead();

                }

                /// content end.
                if (equalsSequence("<", "/", "dsd", ">")) {
                    return n;
                }

            } else {
                // error.
            }
        }
        error = true;
        n.meta = "ERROR";
        n.type = "ERROR:dsd";
        return n;
    }


    /*
     RULE	 ::=  	<declare> DECLARATION* </declare>
     | 	<require> BOOLEXP* </require>
     | 	<if> BOOLEXP RULE* </if>
     | 	<rule ref="PENAME"/>
     | 	UNIQUE
     | 	POINTER
     */
    Node rule() throws SyntaxException {
        d.resetLookAhead();
        // todo  - code
        Node n = new Node();
        n.meta = "RULE";
        String first = d.lookAhead(), second = d.lookAhead(), third = d.lookAhead();
        Node child = null;
        n.type = second;
        if ("<".equals(first) && ">".equals(third) && equalsOR(second, "declare", "require", "if")) {
            d.consume(3);
            if ("require".equalsIgnoreCase(second)) {
                while ((child = boolexp()) != null) {
                    n.addChild(child);
                }

            } else if ("declare".equalsIgnoreCase(second)) {
                while ((child = declaration()) != null) {
                    n.addChild(child);
                }
            } else {
                child = boolexp();
                if (child == null) {
                    throw new SyntaxException("BOOLEXP missing in rule.");
                }
                n.addChild(child);
                while ((child = rule()) != null) {
                    n.addChild(child);
                }
            }

            if (!equalsSequence("<", "/", second, ">")) {
                throw new SyntaxException("xml syntax error.");
            }

        } else if (e("<", first, "rule", second, "ref", third)) {
            // todo handle pename
            d.consume(2);
            Attribute a = this.getAttributePename("ref");
            if (a == null || !equalsSequence("/", ">")) {
                throw new SyntaxException("ref missing");
            }
            n.attributes.add(a);
            n.inline = true;
//
        } else {
            if ((n = unique()) != null || (n = pointer()) != null) {
                return n;
            } else {
                throw new SyntaxException("empty token.");
            }
        }

//        } else {
//            n.meta = "ERROR";
//        }
//        this.error = true;
        n.type = "ruuuule";
        return n;
    }

    void rule_declareRequireIf(Node n, String type) throws SyntaxException {
        d.resetLookAhead();
        String one = d.lookAhead(), two = d.lookAhead();

        while ("<".equals(one) && !"/".equals(two)) {
            System.out.println("while" + one);
            Node x = null;
            if ("declare".equalsIgnoreCase(type)) {
                x = declaration();
            } else if ("require".equalsIgnoreCase(type)) {
                while ((x = boolexp()) != null) {
                    System.out.println("adding child");
                    n.addChild(x);
                }
            } else if ("if".equalsIgnoreCase(type)) {
                // todo boolexp rule.
            } else {
                throw new SyntaxException("Invalid token: " + type);
            }
//            n.addChild(x);
            one = d.lookAhead();
            two = d.lookAhead();
        }
        d.resetLookAhead();

    }

    /*
    DECLARATION	 ::=  	ATTRIBUTEDECL
	| 	<required> ATTRIBUTEDECL* </required>
	| 	<contents> ( REGEXP  |  NORMALIZE  |  CONTENTSDEFAULT )* </contents>
     */
    Node declaration() throws SyntaxException {
        return null;
    }


    /*
   ATTRIBUTEDECL	 ::=  	<attribute  ( name="PANAME" )? ( type="ATTRTYPE" )? >
( REGEXP  |  NORMALIZE  |  ATTRIBUTEDEFAULT )*
</attribute>
    */
    Node attributedecl() throws SyntaxException {
        if (equalsSequence("<", "attribute")) {
            String first = d.lookAhead(), second = d.lookAhead();
            if (!">".equals(first)) {

            }

        } else {
            throw new SyntaxException();
        }
        return null;
    }

    String paname() {
        return null;
    }

    /*
    ATTRTYPE	 ::=  	string  |  qname  |  qaname
     */
    Attribute _attrtype() throws SyntaxException {
        d.resetLookAhead();
        if (equalsOR(d.lookAhead(), "\"string\"", "\"qname\"", "\"qaname\"")) {
            Attribute a = new Attribute();
            a.type = "type";
            a.value = d.nextToken();
            return a;
        }
        return null;
    }

    /*
    BOOLEXP	 ::=  	<and> BOOLEXP* </and>
	| 	<or> BOOLEXP* </or>
	| 	<not> BOOLEXP </not>
	| 	<imply> BOOLEXP BOOLEXP </imply>
	| 	<equiv> BOOLEXP* </equiv>
	| 	<one> BOOLEXP* </one>
	| 	<parent> BOOLEXP </parent>
	| 	<ancestor> BOOLEXP </ancestor>
	| 	<child> BOOLEXP </child>
	| 	<descendant> BOOLEXP </descendant>
	| 	<this/>
	| 	<element ( name="PENAME" )? />
	| 	<attribute ( name="PANAME" )? > REGEXP? </attribute>
	| 	<contents> REGEXP* </contents>
	| 	<boolexp ref="PENAME"/>
     */
    Node boolexp() throws SyntaxException {
        d.resetLookAhead();
        String first = d.lookAhead(), second = d.lookAhead(), third = d.lookAhead();
        Node n = new Node();
        boolean entered = true;
//        boolean incorrect = false;
//        String type = second;
        d.resetLookAhead();
        n.type = second;
        n.meta = "BOOLEXP";
//        if ("<".equals(first)) {

        if (!"/".equals(second) && e(first, "<", third, ">")) {
            d.consume(3);

            if (equalsOR(second, "and", "or", "equiv", "one")) {
                Node child = null;
                while ((child = boolexp()) != null) {   // kleene star.
                    n.addChild(child);     // todo check validity.
                }
            } else if (equalsOR(second, "not", "parent", "ancestor", "child", "descendant")) {
                n.addChild(boolexp());
            } else if ("imply".equalsIgnoreCase(second)) {
                n.addChild(boolexp());
                n.addChild(boolexp());
            } else if ("attribute".equalsIgnoreCase(second)) {
                // no attributes present in this case.
                Node child = regexp();
                if (child != null) {
                    n.addChild(child);
                }
                if (equalsSequence("<", "/", second, ">")) {
                    return n;
                }
//                }
                // otherwise.
                throw new SyntaxException("Invalid 'attribute' element declaration ");


            } else {
                d.back();
                d.back();
                d.back(); // release consumed tokens again.
                entered = false;
            }

            if (entered) {
                if (equalsSequence("<", "/", second, ">"))
                    return n;
            }
        } else if (e(first, "<")) {
            if ("this".equals(second)) {
                d.consume(3);
                n.inline = true;
                if ("/".equals(third) && ">".equals(d.nextToken())) {
//                     n.type = "this";
                    return n;

                } else {
                    throw new SyntaxException("Invalid this element.");
                }

            } else if ("element".equalsIgnoreCase(second)) {
                d.consume(2);
                Attribute name = getAttributePename("name");
                n.attributes.add(name);
                if (equalsSequence("/", ">")) {
                    return n;
                } else {
                    throw new SyntaxException("Invalid element declaration ");

                }

            } else if ("attribute".equalsIgnoreCase(second)) {
//                d.consume(3);
                d.consume(2);
                Attribute name = getAttributePename("name");
                n.attributes.add(name);
                if (equalsSequence(">")) {
                    Node child = regexp();
                    if (child != null) {
                        n.addChild(child);
                    }
                    if (equalsSequence("<", "/", second, ">")) {
                        return n;
                    }
                }
                // otherwise.
                throw new SyntaxException("Invalid attribute element declaration ");


            } else if ("this".equals(second)) {
                if (equalsSequence("<", "this", "/", ">")) {
                    return n;
                }
                throw new SyntaxException("invalid this staement.");

            } else if ("contents".equalsIgnoreCase(second)) {
                d.consume(3);
                if (">".equals(third)) {
                    Node child = null;
                    while ((child = regexp()) != null) {
                        n.addChild(child);
                    }
                    if (equalsSequence("<", "/", second, ">")) {
                        return n;
                    }
                }
                throw new SyntaxException("Invalid 'content' declaration ");

            } else if ("boolexp".equalsIgnoreCase(second)) {
//                d.consume(3);
                d.consume(2);
                Attribute a = this.getAttributePename("ref");
                if (a != null) {
                    n.attributes.add(a);
                    if (equalsSequence("/", ">")) {
                        return n;
                    }
                }
                throw new SyntaxException("Invalid boolexp element declaration ");
            } else {
                entered = false;
            }
        } else {
            entered = false;
        }


        if (entered) {
            throw new SyntaxException("BOOLEXP tag");
        }

        return null;
    }


    Attribute getAttributePename(String name) throws SyntaxException {
        String token = d.lookAhead();
        // todo value validation.
        while (!">".equals(token) && !"/".equals(token)) {
            token = d.nextToken();
            if (name.equals(token)) {
                token = d.nextToken();
                if (!"=".equals(token)) {
                    throw new SyntaxException("Invalid attribute declaration: " + name);
                }
                if ((token = d.nextToken()).equals(">")) {
                    throw new SyntaxException("Invalid attribute declaration: " + name);
                }
                Attribute a = new Attribute();
                a.type = name;
                a.value = token;
                return a;
            }

            token = d.lookAhead();
        }
        return null;
    }

    // returns attribute list. assumes xml syntax is correct.
    ArrayList<Attribute> getAttributes(String... names) throws SyntaxException {
        ArrayList<Attribute> al = new ArrayList<Attribute>();
        String token = d.lookAhead();
        // todo value validation.
        int i = 0;
        while (!">".equals(token) && !"/".equals(token)) {
            token = d.nextToken();
//            if (name.equals(token)) {
            for (String name : names) {
                if (name.equals(token)) {

                    token = d.nextToken();
                    if (!"=".equals(token)) {
                        throw new SyntaxException("Invalid attribute declaration: " + name);
                    }
                    if ((token = d.nextToken()).equals(">")) {
                        throw new SyntaxException("Invalid attribute declaration: " + name);
                    }
                    Attribute a = new Attribute();
                    a.type = name;
                    a.value = token;
                    al.add(a);
                }
            }

            token = d.lookAhead();
        }
        return al.size() > 0 ? al : null;
    }

    /*
    REGEXP	 ::=  	<sequence> REGEXP* </sequence>
	| 	<optional> REGEXP </optional>
	| 	<complement> REGEXP </complement>
	| 	<union> REGEXP* </union>
	| 	<intersection> REGEXP* </intersection>
	| 	<minus> REGEXP REGEXP </minus>
	| 	<repeat ( ( number="NUMERAL" )?  |  ( min="NUMERAL" )? ( max="NUMERAL" )? ) > REGEXP </repeat>
	| 	<string ( value="VALUE" )? />
	| 	<char ( ( set="VALUE" )?  |  min="CHAR" max="CHAR" ) />
	| 	<stringtype ref="PENAME"/>
	| 	<contenttype ref="PENAME"/>
	| 	BOOLEXP
     */
    Node regexp() throws SyntaxException {


        return null;
    }


    /*
    DEFINITION	 ::=  	<rule id="PENAME"> RULE* </rule>
   | 	<contenttype id="PENAME"> REGEXP </contenttype>
   | 	<stringtype id="PENAME"> REGEXP </stringtype>
   | 	<boolexp id="PENAME"> BOOLEXP </boolexp>
    */
    Node definition() throws SyntaxException {

        return null;
    }

    /*
    NORMALIZE	 ::=  	<normalize  ( whitespace="WHITESPACE" )?  ( case="CASE" )? />
     */
    Node normalize() throws SyntaxException {
        return null;
    }

    /*
    WHITESPACE	 ::=  	preserve  |  compress  |  trim
     */
    Node whitespace() throws SyntaxException {
        return null;
    }

    /*
    CASE	 ::=  	preserve  |  upper  |  lower
     */
    Node Case() throws SyntaxException {
        return null;
    }

    /*
    ATTRIBUTEDEFAULT	 ::=  	<default value="VALUE" />
     */
    Node attributeDefault() throws SyntaxException {
        return null;
    }

    /*
    CONTENTSDEFAULT	 ::=  	<default> ANYCONTENTS </default>
    */
    Node contentsDefault() throws SyntaxException {
        return null;
    }

    /*
UNIQUE	 ::= 	<unique ( key="VALUE" )? >
  ( BOOLEXP FIELD* | ( <select> BOOLEXP FIELD* </select> )* )
</unique>

     */
    Node unique() throws SyntaxException {
        Node n = new Node();
        n.meta = "UNIQUE";
        if (equalsSequence("<", "unique")) {
            String first = d.nextToken();
            if (first.equals(">")) {

            } else if (first.equalsIgnoreCase("key") && d.nextToken().equals("=")) {

            }
        }
        error = true;
        n.meta = "ERROR";
        n.type = "ERROR:unique";
        return n;
    }


    /*
    POINTER	 ::= 	<pointer ( key="VALUE" )? > BOOLEXP? FIELD* </pointer>
     */
    Node pointer() throws SyntaxException {

        return null;
    }


    /*
    FIELD	 ::=  	<attributefield name="PANAME" ( type="ATTRTYPE" )? > BOOLEXP? </attributefield>
	| 	<chardatafield ( type="ATTRTYPE" )? > BOOLEXP? </chardatafield>
     */
    Node field() throws SyntaxException {
        return null;
    }


    // returns true if x matches one of the targets.
    private boolean equalsOR(String x, String... targets) {
        for (String item : targets) {
            if (x.equalsIgnoreCase(item)) {
                return true;
            }
        }
        return false;
    }

    /////////////////////////////////////////////////////////////
    // util methods

    // checks whether next sequence of tokens match the params.

    private boolean equalsSequence(String... items) {
        for (String item : items) {
            if (!item.equalsIgnoreCase(d.nextToken())) {
                return false;
            }
        }
        return true;
    }

    private boolean equalsLookAhead(String... items) {
        for (String item : items) {
            if (!item.equalsIgnoreCase(d.lookAhead())) {
//                d.resetLookAhead();
                return false;
            }
        }
//        d.resetLookAhead();
        return true;
    }

    // compares two consecutive items. number of params must be even.
    private boolean e(String... items) {
        if (items.length % 2 == 1) {
            return false;
        }
        for (int i = 0; i < items.length; i += 2) {
            if (!items[i].equalsIgnoreCase(items[i + 1])) {
                return false;
            }
        }
        return true;
    }

}
